local AceOO = AceLibrary("AceOO-2.0")
local waterfall = AceLibrary("Waterfall-1.0")

IIB_BarBase = AceOO.Interface{
	SetButtonSize = "function",
	SetButtonLocation = "function",
	SetPosition = "function",
	NewButton = "function",
	UpdateHeaderState = "function",
	UpdateButtonState = "function",
}

IIB_Theme = AceOO.Interface{
	EnableTheme = "function", -- (button)
	DisableTheme = "function", -- (button)
	SetFlash = "function", -- (button,r,g,b)
	SetOverlay = "function", -- (button,r,g,b)
	ClearFlash = "function", -- (button)
	ClearOverlay = "function", -- (button)
}

local IBDefaultTheme = AceOO.Class(IIB_Theme)

function IBDefaultTheme.prototype:init(name)
	self.class.super.prototype.init(self)
end

function IBDefaultTheme.prototype:EnableTheme(button)
	button:SetPushedTexture("Interface\\Buttons\\UI-Quickslot-Depress")
	button:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
	button:GetHighlightTexture():SetBlendMode("ADD")
	button:SetCheckedTexture("Interface\\Buttons\\CheckButtonHilight")
	button:GetCheckedTexture():SetBlendMode("ADD")
	button.Flash:SetTexture("Interface\\Buttons\\UI-QuickslotRed")
	button.Flash:SetBlendMode("ADD")
	button.Flash:Hide()
	button:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
	local NormalTexture = button:GetNormalTexture()
	NormalTexture:SetWidth(60)
	NormalTexture:SetHeight(60)
	NormalTexture:ClearAllPoints()
	NormalTexture:SetPoint("CENTER", 1, -1)
	button.oldOnSizeChanged = button:GetScript("OnSizeChanged")
end

function IBDefaultTheme.prototype:DisableTheme(button)
	-- for now just restore the previous OnSizeChanged handler
end

function IBDefaultTheme.prototype:SetFlash(button,r,g,b)
	button:SetChecked(1)
	button:GetCheckedTexture():SetVertexColor(r,g,b)
end

function IBDefaultTheme.prototype:SetOverlay(button,r,g,b)
	button.Icon:SetVertexColor(r,g,b)
end

function IBDefaultTheme.prototype:ClearFlash(button)
	button:SetChecked(0)
end

function IBDefaultTheme.prototype:ClearOverlay(button)
	button.Icon:SetVertexColor(1,1,1)
end

local function activate(self, oldLib, oldDeactivate)
	if oldDeactivate then
		oldDeactivate(oldLib)
	end
end

AceLibrary:Register(IBDefaultTheme,"IBDefaultTheme-1.0",1,activate)

InfiniBar = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceDB-2.0", "FuBarPlugin-2.0")
local InfiniBar = InfiniBar
InfiniBar.hasNoText = true
InfiniBar.hasIcon = true
--InfiniBar.overrideMenu = true
InfiniBar.defaultPosition = "RIGHT"
InfiniBar.defaultMinimapPosition = 90
InfiniBar.clickableTooltip = true

function InfiniBar:OnEnable()
	self:RegisterEvent("SPELLS_CHANGED","RefreshSpellCache")
	self:RegisterEvent("PLAYER_LOGIN","RefreshSpellCache")
	self:RefreshSpellCache()
	self:RegisterDB("InfiniBarDB","InfiniBarDBPC")
	if self.db.char.bars then
		for k,v in pairs(self.db.char.bars) do
			if not v[0] and v[1] then
				v[1].uid = nil
				local type = v[1].type
				self.db.char.bars[k] = AceLibrary(type):Deserialize(unpack(v))
			end
		end
	end
	InfiniBar:RegisterGUI()
end

function InfiniBar:RefreshSpellCache()
	self.spellcache = {}
	self.petcache = {}
	local i = 1
	while i > 0 do
		local sName, sRank = GetSpellName(i, BOOKTYPE_SPELL)
		if not sName then
			i = 0
		else
			self.spellcache[string.lower(sName..'('..sRank..')')] = i
			self.spellcache[string.lower(sName)] = i
			self.spellcache[string.lower(sName..'()')] = i
			i = i + 1
		end
	end
	i = 1
	while i > 0 do
		local sName, sRank = GetSpellName(i, BOOKTYPE_PET)
		if not sName then
			i = 0
		else
			self.spellcache['PET'..string.lower(sName..'('..sRank..')')] = i
			self.spellcache['PET'..string.lower(sName)] = i
			self.spellcache['PET'..string.lower(sName..'()')] = i
			self.petcache[string.lower(sName..'('..sRank..')')] = i
			i = i + 1
		end
	end
end

function InfiniBar:GetSpellInfo(sName)
	if not sName then return end
	--DEFAULT_CHAT_FRAME:AddMessage("GetSpellInfo('"..tostring(sName).."') = "..tostring(self.spellcache[sName]))
	return self.spellcache[string.lower(sName)] or self.spellcache['PET'..string.lower(sName)],
		self.spellcache[string.lower(sName)] and 'spell' or 'pet'
end

function InfiniBar:GetPetSpellInfo(sName)
	if not sName then return end
	return self.petcache[string.lower(sName)]
end

InfiniBar.flashPriority = {
	AutoCast = 1,
	Channeling = 2,
	Casting = 3,
	Buffed = 4,
	Debuffed = 5,
	Equipped = 6,
}

InfiniBar.ovrlyPriority = {
	InCooldown = 1,
	UnusableItem = 2,
	UnusableAction = 3,
	OutOfMana = 4,
	NotInRange = 5,
}

InfiniBar.highlightColor = {
	Unlocked = {160/255,32/255,240/255},
	Channeling = {0/255,0/255,180/255},
	AutoCast = {240/255,240/255,240/255},
	Casting = {180/255,180/255,0/255},
	Buffed = {0/255,180/255,0/255},
	DeBuffed = {180/255,0/255,0/255},
	Equipped = {200/255,140/255,0/255},
	OutOfMana = {0/255,0/255,240/255},
	NotInRange = {200/255,0/255,0/255},
	InCooldown = {160/255,160/255,160/255},
	UnusableItem = {160/255,160/255,160/255},
	UnusableAction = {160/255,160/255,160/255},
}

InfiniBar.textSubs = {}
InfiniBar.textTriggers = {}
InfiniBar.statusTriggers = {}
InfiniBar.barList = {}
InfiniBar.registeredBar = {}
InfiniBar.sortedList = {}

function InfiniBar:ParseTextSub(tSub,bar,btn,...)
	local t = {...}
	return string.gsub(tSub, "(%[.-%])", function(s)
		s = s:sub(2,-2) -- this strips off the []'s
		local _,_,new,opt = string.find(s,"(%S+)%s+(.+)")
		if new then s = new end
		if type(InfiniBar.textSubs[s]) == "function" then
			return InfiniBar.textSubs[s](bar,btn,opt,unpack(t))
		elseif type(InfiniBar.textSubs[s]) == "string" then
			return InfiniBar.textSubs[s]
		end
		return ""
	end)
end

function InfiniBar:RegisterTextSub(cmd,trigger,subFunc)
	InfiniBar.textSubs[cmd] = subFunc
	if trigger then
		InfiniBar.textTriggers[trigger] = true
		self:RegisterBarEvent(trigger)
	end
end

-- the func passed is expected to take as arguments, (bar, buttonIndex, event, ...) where ... are the arguments passed for the given event.
function InfiniBar:RegisterStatus(trigger,func)
	InfiniBar.statusTriggers[trigger] = InfiniBar.statusTriggers[trigger] or {}
	InfiniBar.statusTriggers[trigger][#InfiniBar.statusTriggers[trigger]+1] = func
	self:RegisterBarEvent(trigger)
end

function InfiniBar:RegisterStatusColor(colorType,name,r,g,b)
	if InfiniBar.highlightColor[name] then return end
	local priorityTable
	if colorType == "overlay" then
		priorityTable = InfiniBar.ovrlyPriority
	elseif colorType == "flash" then
		priorityTable = InfiniBar.flashPriority
	else
		return
	end
	InfiniBar.highlightColor[name] = {r,g,b}
	for k,v in pairs(priorityTable) do
		priorityTable[k] = v + 1
	end
	priorityTable[name] = 1
end

function InfiniBar:RegisterBar(bar)
	InfiniBar.barList[bar.uid] = bar
	InfiniBar.registeredBar[bar.name or bar.uid] = bar
	InfiniBar.sortedList[#InfiniBar.sortedList+1] = bar.name or bar.uid
	table.sort(InfiniBar.sortedList)
end

function InfiniBar:UnregisterBar(bar)
	InfiniBar.barList[bar.uid] = nil
	InfiniBar.registeredBar[bar.name or bar.uid] = nil
	local t = {}
	local n = 0
	for i,v in ipairs(InfiniBar.sortedList) do
		if v ~= bar.name and v ~= bar.uid then
			t[i-n] = v
		else
			n = n + 1
		end
	end
	InfiniBar.sortedList = t
end

function InfiniBar:BarExists(barname)
	return not not InfiniBar.registeredBar[barname]
end

function InfiniBar:GetBar(barname)
	return InfiniBar.registeredBar[barname]
end

function InfiniBar:SaveBarToCharProfile(barName)
	self.db.char.bars = self.db.char.bars or {}
	self.db.char.bars[barName] = InfiniBar.registeredBar[barName]
end

function InfiniBar:BuildBarList()
	local t = {}
	for k in pairs(InfiniBar.registeredBar) do
		t[k] = true
	end
	return t
end

function InfiniBar:BuildButtonList(bar)
	return InfiniBar.registeredBar[bar]:BuildButtonList()
end

function InfiniBar:RegisterBarEvent(event)
	if not self:IsEventRegistered(event) then
		self:RegisterEvent(event,"TriggerBarEvent",0.5)
	end
end

function InfiniBar:TriggerBarEvent(...)
	local event = AceLibrary("AceEvent-2.0").currentEvent
	if not InfiniBar.textTriggers[event] then
		if not InfiniBar.statusTriggers[event] then
			for _,bar in pairs(InfiniBar.barList) do
				bar:TriggerBarEvent(event,...)
			end
		else
			for _,bar in pairs(InfiniBar.barList) do
				bar:TriggerBarEvent(event,...)
				for i,func in ipairs(InfiniBar.statusTriggers[event]) do
					bar:PerButton(func,event,...)
				end
			end
		end
	else
		for _,bar in pairs(InfiniBar.barList) do
			bar:TriggerBarEvent(event,...)
			bar:UpdateTexts(nil,...)
		end
	end
end

function InfiniBar.Label(text,width,waterfall,...)
	if width then
		waterfall:AddControl('type','label','text',text,'width',width,...)
	else
		waterfall:AddControl('type','label','text',text,...)
	end
end

function InfiniBar.PreLabel(text,width,waterfall,...)
	InfiniBar.Label(text,width,waterfall,'noNewLine',true,...)
end

function InfiniBar.Heading(text,width,waterfall,...)
	if width then
		waterfall:AddControl('type','heading','text',text,'width',width,...)
	else
		waterfall:AddControl('type','heading','text',text,...)
	end
end

function InfiniBar.Dropdown(table,key,list,width,waterfall,setFunc,...)
	if width then
		waterfall:AddControl('type','dropdown','strict',true,
			'text',(type(table[key]) == "string") and table[key] or list[table[key]],
			'getFunc',function() return table[key] end,
			'setFunc',function(v) table[key] = v if setFunc then setFunc() end end,
			'list',list,'fullRefresh',true,'width',width,...)
	else
		waterfall:AddControl('type','dropdown','strict',true,
			'text',(type(table[key]) == "string") and table[key] or list[table[key]],
			'getFunc',function() return table[key] end,
			'setFunc',function(v) table[key] = v if setFunc then setFunc() end end,
			'list',list,'fullRefresh',true,...)
	end
end

function InfiniBar.DropdownFunc(getFunc,setFunc,list,width,waterfall,...)
	if width then
		waterfall:AddControl('type','dropdown','strict',true,
			'text',getFunc(),
			'getFunc',getFunc,'setFunc',setFunc,'list',list,'fullRefresh',true,'width',width,...)
	else
		waterfall:AddControl('type','dropdown','strict',true,
			'text',getFunc(),
			'getFunc',getFunc,'setFunc',setFunc,'list',list,'fullRefresh',true,...)
	end
end

function InfiniBar.Button(text,func,width,waterfall,...)
	if width then
		waterfall:AddControl('type','button','execFunc',func,'text',text,'width',width,...)
	else
		waterfall:AddControl('type','button','execFunc',func,'text',text,...)
	end
end

function InfiniBar.TextBox(getFunc,setFunc,width,waterfall,...)
	local curValue = getFunc()
	if width then
		waterfall:AddControl('type','textbox','text',curValue,'getFunc',getFunc,'setFunc',setFunc,'width',width,...)
	else
		waterfall:AddControl('type','textbox','text',curValue,'getFunc',getFunc,'setFunc',setFunc,...)
	end
end

function InfiniBar.NumBox(getFunc,setFunc,width,waterfall,...)
	InfiniBar.TextBox(getFunc,setFunc,width,waterfall,'toNumber',true,...)
end

function InfiniBar.Slider(getFunc,setFunc,min,max,step,width,waterfall,...)
	if width then
		waterfall:AddControl('type','slider','getFunc',getFunc,'setFunc',setFunc,'min',min,'max',max,'step',step,'width',width,...)
	else
		waterfall:AddControl('type','slider','getFunc',getFunc,'setFunc',setFunc,'min',min,'max',max,'step',step,...)
	end
end

function InfiniBar.Keybind(getFunc,setFunc,width,waterfall,...)
	if width then
		waterfall:AddControl('type','keybind','getFunc',getFunc,'setFunc',setFunc,'width',width,...)
	else
		waterfall:AddControl('type','keybind','getFunc',getFunc,'setFunc',setFunc,...)
	end
end

function InfiniBar.Checkbox(text,getFunc,setFunc,width,waterfall,...)
	if width then
		waterfall:AddControl('type','checkbox','text',text,'getFunc',getFunc,'setFunc',setFunc,'width',width,...)
	else
		waterfall:AddControl('type','checkbox','text',text,'getFunc',getFunc,'setFunc',setFunc,...)
	end
end

function InfiniBar.Tooltip(title,text)
	return 'tooltipTitle',title,'tooltipText',text
end

function InfiniBar:RegisterGUI()
	waterfall:Register("InfiniBar",'title','InfiniBar',
		'defaultPane',"InfiniBar",
		'tree',{
			{
				text = "InfiniBar",
				id = "InfiniBar",
				isSelected = true,
			}
		},'children',function(id)
			if id == "InfiniBar" then
				self:BuildGUIPane()
			end
		end)
end

function InfiniBar:OpenGUI()
	waterfall:Open("InfiniBar","InfiniBar")
end


local barClass
StaticPopupDialogs["INFINIBAR_ADDBAR"] = {
	text = "Choose the name for the new Bar.",
	button1 = "Accept", button2 = "Cancel",
	OnAccept = function()
		local newname = getglobal(this:GetParent():GetName().."EditBox"):GetText()
		if not InfiniBar:BarExists(newname) then
			barClass = barClass or AceLibrary("IBInfiniBar-1.0")
			local bar = barClass:new(newname)
			bar:SetBarTheme("IBDefaultTheme-1.0")
			bar:DefineStates({{name="actionbar", min=1, max=1, short="A", [1]=1, stateconst=1}})
			bar:SetPosition(0,0,"TOPLEFT","TOPLEFT")
			InfiniBar:SaveBarToCharProfile(newname)
			InfiniBar.configBar = newname
			waterfall:Refresh("InfiniBar")
		end
	end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1,
	hasEditBox = 1,
}

function InfiniBar:DeleteConfigBar()
	local barName = self.configBar
	self.configBar = nil
	self.db.char.bars[barName]:DeleteBar()
	InfiniBar:UnregisterBar(self.db.char.bars[barName])
	self.db.char.bars[barName] = nil
end

StaticPopupDialogs["INFINIBAR_DELBAR"] = {
	text = "Do you really want to delete this InfiniBar?",
	button1 = "Yes", button2 = "No",
	OnAccept = function()
		InfiniBar:DeleteConfigBar()
		waterfall:Refresh("InfiniBar")
	end,
	timeout = 0,
	whileDead = 1,
	hideOnEscape = 1,
}

function InfiniBar:BuildGUIPane()
	-- label, dropdown
	InfiniBar.Label('Bar:',50,waterfall)
	InfiniBar.Dropdown(self,'configBar',self.sortedList,200,waterfall,nil,'noNewLine',true)
	-- button, button
	InfiniBar.Button('Add Bar',function() StaticPopup_Show("INFINIBAR_ADDBAR") end,
		100,waterfall)
	InfiniBar.PreLabel('',50,waterfall)
	if self.configBar and
		 self.registeredBar[self.configBar] then
		InfiniBar.Button('Delete Bar',function() StaticPopup_Show("INFINIBAR_DELBAR") end,
			100,waterfall,'noNewLine',true)
		-- include bar config
		self.registeredBar[self.configBar]:BuildBarGUI(waterfall)
	else
		InfiniBar.Button('Delete Bar',function() end,100,waterfall,'disabled',true,'noNewLine',true)
	end
end

function InfiniBar:OnClick(button)
	if button ~= "LeftButton" then return end
	self:OpenGUI()
end

--=== Text Substitutions ===--

InfiniBar:RegisterTextSub("itemcount","BAG_UPDATE",function(bar,btn,opt)
	if bar:GetButtonEffectiveState("type",btn) == "item" then
		return GetItemCount(bar:GetButtonEffectiveMetaData("item",btn))
	end
	return ""
end)

InfiniBar:RegisterTextSub("key","UPDATE_BINDINGS",function(bar,btn,opt)
	return bar:GetButtonBinding(btn) or ""
end)

InfiniBar:RegisterTextSub("btnnum",nil,function(bar,btn,opt)
	return btn or ""
end)
